/* 
 * Copyright 2011 kOSMik.
 * This file is part of kOSMik.
 * 
 * kOSMik is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * kOSMik is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with kOSMik.  If not, see <http://www.gnu.org/licenses/>.
 *  
 * kOSMik:
 * Open Street Map - Editor.
 * http://code.google.com/p/swp-dv-ws2010-osm-2/
 */

package de.fuberlin.kosmik.gui.view;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import android.app.Activity;
import org.mapsforge.android.maps.ArrayItemizedOverlay;
import org.mapsforge.android.maps.ArrayWayOverlay;
import org.mapsforge.android.maps.GeoPoint;
import org.mapsforge.android.maps.MapView;
import org.mapsforge.android.maps.MapViewMode;
import org.mapsforge.android.maps.OverlayItem;
import org.mapsforge.android.maps.OverlayWay;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.AttributeSet;
import android.util.Log;
import de.fuberlin.kosmik.R;
import de.fuberlin.kosmik.data.DataBaseConnector;
import de.fuberlin.kosmik.data.DataBaseConnectorImpl;
import de.fuberlin.kosmik.data.dao.Poi;
import de.fuberlin.kosmik.data.dao.TrackData;
import de.fuberlin.kosmik.log.Constants;
import de.fuberlin.kosmik.settings.SettingsChangedListener;
import de.fuberlin.kosmik.trackrecorder.TrackRecorderService;

/**
 * MapFrame extends mapsforge's MapView. It shows OSM - informations using the
 * mapsforge - library. In order to load a map, an internet permission is
 * required.
 * 
 */
public class MapFrame extends MapView implements ServiceLocationListener,
		SettingsChangedListener {

	private static final long serialVersionUID = 8389908166410188510L;

	private TrackRecorderService trackRecorderService;
	private long trackId;
	private GeoPoint currentLocation;
	private String tag = Constants.MAP_FRAME;
	private SharedPreferences mapPreferences;

	private enum RecordType {
		TRACK, WAY, AREA, NOT_RECORDING
	}

	private RecordType nowRecording;

	private ArrayItemizedOverlay poisOverlay;

	private ArrayWayOverlay wayOverlay;

	private OverlayWay onTheFlyTrackWay;
	private OverlayWay onTheFlyWayWay;
	private OverlayWay onTheFlyAreaWay;

	private ArrayList<GeoPoint> currentTrackGeoPoints;
	private ArrayList<GeoPoint> currentWayGeoPoints;
	private ArrayList<GeoPoint> currentAreaGeoPoints;

	private boolean followPosition;
	private DataBaseConnector dbc = new DataBaseConnectorImpl(this.getContext());

	private Activity activity;
	/**
	 * First constructor.
	 * 
	 * @param context context of the calling instance
	 */
	public MapFrame(Context context) {
		super(context);
		mapPreferences = getPreferences();
		activity = (Activity)context;
		initMap();
	}

	/**
	 * Second constructor.
	 * 
	 * @param context context of the calling instance
	 * @param attrs attribute sets as given from the calling instance
	 */
	public MapFrame(Context context, AttributeSet attrs) {
		super(context, attrs);
		activity = (Activity)context;

	}

	/**
	 * Redraws the map with all items of the current track.
	 */
	public void initMap() {

		GeoPoint centerPos = null;

		this.poisOverlay = new PoiItemizedOverlay(activity);
		this.getController().setZoom(15);
		//this.setMemoryCardCachePersistence(true);
		// this.setMemoryCardCacheSize(40);
		this.wayOverlay = ViewUtils.arrayWayOverlay();
		this.setClickable(true);
		this.setBuiltInZoomControls(true);
		this.setScaleBar(true);
		this.setMapViewMode(MapViewMode.MAPNIK_TILE_DOWNLOAD);

		if (trackId > 0) {
			TrackData trackData = getTrackData();
			centerPos = trackData.getCentralGeoPoint();

			for (Map.Entry<Long, List<GeoPoint>> wayEntry : trackData.pointMap.entrySet()) {
				Long wayId = wayEntry.getKey();
				if (wayId == null) {
					Log.w(tag, "wayId is null for trackId " + trackId);
				}
				Boolean isArea = trackData.wayIsAreaMap.get(wayId);
				if (isArea == null) {
					Log.w(tag, "isArea is null for way " + wayId);
					isArea = Boolean.valueOf(false);
				}
				Boolean isWay = Boolean.valueOf(wayEntry.getKey().longValue() > 0);
				List<GeoPoint> geoPoints = wayEntry.getValue();

				if (isArea.booleanValue()) {
					geoPoints.add(geoPoints.get(0));
				}

				addWay(geoPoints, isWay.booleanValue(), isArea.booleanValue());
			}

			Collection<Poi> pois = trackData.getPois();

			for (Poi poi : pois) {
				OverlayItem poiOverlayItem = new OverlayPoiItem(poi, getContext());
				poisOverlay.addItem(poiOverlayItem);
			}

		}

		this.getOverlays().add(this.wayOverlay);
		this.getOverlays().add(this.poisOverlay);

		if (centerPos == null) {
			centerPos = restoreSavedPosition();

		}
		if (centerPos != null) {
			getController().setCenter(centerPos);

		}

	}

	@Override
	public void onLocationUpdate(double longitude, double latitude) {

		currentLocation = new GeoPoint(latitude, longitude);

		if (nowRecording != RecordType.NOT_RECORDING) { // if recording

			if (nowRecording == RecordType.WAY) {
				currentWayGeoPoints.add(currentLocation);
				wayOverlay.removeWay(onTheFlyWayWay);

				onTheFlyWayWay.setWayData(ViewUtils.toGeoPointArray(currentWayGeoPoints));
				wayOverlay.addWay(onTheFlyWayWay);

			} else if (nowRecording == RecordType.AREA) {
				currentAreaGeoPoints.add(currentLocation);
				wayOverlay.removeWay(onTheFlyAreaWay);
				onTheFlyAreaWay.setWayData(ViewUtils.toGeoPointArray(currentAreaGeoPoints));
				wayOverlay.addWay(onTheFlyAreaWay);

			} else if (nowRecording == RecordType.TRACK) {
				currentTrackGeoPoints.add(currentLocation);
				wayOverlay.removeWay(onTheFlyTrackWay);
				onTheFlyTrackWay.setWayData(ViewUtils.toGeoPointArray(currentTrackGeoPoints));
				wayOverlay.addWay(onTheFlyTrackWay);

			}

		}

		if (followPosition) {

			this.getController().setCenter(new GeoPoint(latitude, longitude));

		}

	}

	@Override
	public void onPOI(double longitude, double latitude, String newTag) {
		// do nothing here
	}

	@Override
	public void onStartWay() {

		this.nowRecording = RecordType.WAY;
		this.currentWayGeoPoints = new ArrayList<GeoPoint>();
		this.onTheFlyWayWay = ViewUtils.overlayWay(getContext(), null, true, false);
		addWay(this.onTheFlyWayWay);

	}

	@Override
	public void onStopWay() {

		this.nowRecording = RecordType.TRACK;
		this.currentTrackGeoPoints = new ArrayList<GeoPoint>();
		this.onTheFlyTrackWay = ViewUtils.overlayWay(getContext(), null, false, false);
		addWay(this.onTheFlyTrackWay);

	}

	@Override
	public void onStartArea() {

		this.nowRecording = RecordType.AREA;
		this.currentAreaGeoPoints = new ArrayList<GeoPoint>();
		this.onTheFlyAreaWay = ViewUtils.overlayWay(getContext(), null, true, true);
		addWay(this.onTheFlyAreaWay);

	}

	@Override
	public void onStopArea() {

		this.nowRecording = RecordType.TRACK;
		this.currentTrackGeoPoints = new ArrayList<GeoPoint>();
		this.onTheFlyTrackWay = ViewUtils.overlayWay(getContext(), null, false, false);
		addWay(this.onTheFlyTrackWay);

	}

	/**
	 * Registers the track recorder service with the MapFrame.
	 * 
	 * @param recorderService the currently working instance of
	 *            TrackRecorderService
	 */
	public void registerService(TrackRecorderService recorderService) {
		this.trackRecorderService = recorderService;
	}

	@Override
	public void mapsColorsChanged() {
		invalidate();
	}

	/**
	 * Set track ID.
	 * 
	 * @param trackId ID of the current track
	 */
	public void setTrackId(long trackId) {
		this.trackId = trackId;
		initMap();
	}

	private TrackData getTrackData() {
		Log.d(tag, String.format("initializing map for track id %d", Long.valueOf(trackId)));
		TrackData trackData = dbc.getTrackData(trackId);
		return trackData;
	}

	/**
	 * Toggles the functionality of the MapFrame to follow the location signals
	 * or just show an old track. Should be called from the starting Activity.
	 * 
	 * @param isRecording true if on the fly recording should be make, false if
	 *            not.
	 */
	public void setIsRecording(boolean isRecording) {
		if (isRecording) {
			this.nowRecording = RecordType.TRACK;

			this.onTheFlyAreaWay = ViewUtils.overlayWay(getContext(), null, true, true);
			this.onTheFlyWayWay = ViewUtils.overlayWay(getContext(), null, true, false);
			this.onTheFlyTrackWay = ViewUtils.overlayWay(getContext(), null, false, false);

			this.currentWayGeoPoints = new ArrayList<GeoPoint>();
			this.currentTrackGeoPoints = new ArrayList<GeoPoint>();
			this.currentAreaGeoPoints = new ArrayList<GeoPoint>();

			this.followPosition = true;
		} else {
			this.nowRecording = RecordType.NOT_RECORDING;
			this.followPosition = false;
		}
	}

	private void addWay(List<GeoPoint> geoPoints, boolean isWay, boolean isArea) {
		if (geoPoints.size() > 1) {
			addWay(ViewUtils.overlayWay(getContext(), geoPoints, isWay, isArea));
		}
	}

	private void addWay(OverlayWay way) {
		if (way.getWayData().length > 1) {
			this.wayOverlay.addWay(way);
		}
	}

	private void savePosition(GeoPoint gp) {
		if (mapPreferences == null) {
			mapPreferences = getPreferences();
		}
		SharedPreferences.Editor editor = mapPreferences.edit();
		editor.putInt("lastLat", gp.getLatitudeE6());
		editor.putInt("lastLng", gp.getLongitudeE6());
		editor.commit();
	}

	private GeoPoint restoreSavedPosition() {
		if (mapPreferences == null) {
			mapPreferences = getPreferences();
		}
		int latE6 = mapPreferences.getInt("lastLat", 0);
		int lngE6 = mapPreferences.getInt("lastLng", 0);
		if (latE6 != 0 && lngE6 != 0) {
			return new GeoPoint(latE6, lngE6);
		}

		return null;
	}

	private SharedPreferences getPreferences() {
		return getContext().getSharedPreferences(
				getResources().getString(R.string.preferences_key_map),
				Context.MODE_WORLD_WRITEABLE);
	}

	/**
	 * Saves current position.
	 */
	public void persist() {
		if (currentLocation != null)
			savePosition(currentLocation);
	}

}
